package com.hfb.mashibing.alip8.system.io;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import org.junit.Test;

/**
 * 下面的几个例子中：
 *      内核态、用户态切换次数不一（时间和资源消耗不同）
 */
public class OSFilesIO {

    static byte[] data = "123456789\n".getBytes();
    static String path = "/Users/HFB/Downloads/out.txt";

    public static void main(String[] args) throws Exception {
      switch (args[0]) {
          case "0":
              testBasicFileIO();
              break;
          case "1":
              testBufferedFileIO();
              break;
          case "2":
              testRandomAccessFileWrite();
              break;
          case "3":
              whatByBuffer();
              break;
          default:
              System.out.println("I 'm default.");
      }

    }

    /**
     * 最基本的 file 写法
     * OS  16字节 writeFile(16字节)
     */
    @Test
    private static void testBasicFileIO() throws Exception {
        File  file = new File(path);
        FileOutputStream out = new FileOutputStream(file);
        while (true) {
//            Thread.sleep(10); //会引发问题
            out.write(data);
        }
    }

    /**
     * 测试  Buffer 文件 IO
     * jvm 8kb syscal write(8kb Byte[])
     * OS  4kb pagedate writeFile(4KB)
     *
     *
     * @throws Exception
     */
    @Test
    private static void testBufferedFileIO() throws Exception  {
        BufferedOutputStream out = new BufferedOutputStream(
                                        new FileOutputStream(
                                            new File(path)));
        while (true) {
//            Thread.sleep(10); //会引发问题
            out.write(data);
        }
    }

    /**
     * 测试文件 NIO
     * @throws Exception
     */
    @Test
    private static void testRandomAccessFileWrite() throws Exception  {
        // 读随机写
        RandomAccessFile out = new RandomAccessFile(path, "rw");
        // 普通写
        out.write("Hello! The World\n".getBytes());
        out.write("Hello! One World\n".getBytes());
        System.out.println("write-----------------");
        System.in.read(); // 程序阻塞

        // 重置开始指针的位置（开始指正的偏移）
        out.seek(4); // 参数是字节数组的下标（即：hello的【l】的位置）
        out.write("ooxx".getBytes());

        System.out.println("seek----------");
        System.in.read();

        // 堆外映射写
        // 文件通道的map方法
        FileChannel channel = out.getChannel();
        // 调用系统内核的 mmap 函数，得到堆外内存和文件映射的内存模型
        MappedByteBuffer map = channel.map(MapMode.READ_WRITE, 0, 4096);

        // 曾经我们需要out.write()这样的系统调用，程序的data才会进入系统内核的 pagecache，
        // 会引起耗时有费力的用户态和内核态切换
        // 但是，现在 put 不是系统调用，但是数据会到达内核的 pagecache
        // 注意：现在的mmap的内存模型，依然是内核的pagecache体系所约束的！！！
        // 换言之，就是依然会丢数据！
        // what?怎么办呢？
        // 你可以去github上找一些C程序员写的jni扩展库，使用linux内核的 Direct IO
        // 直接IO是会忽略linux的pagecache，
        // 实际是把pagecache交给了程序自己开辟一个字节数组模拟/维护pagecache，用代码逻辑维护一致性/dirty...等一系列复杂逻辑
        map.put("@@@".getBytes());

        System.out.println("map--put---------------------");
        System.in.read();

//        map.force();    // == flush

        // buffer在随机读写中的应用
        out.seek(0);
        ByteBuffer buffer = ByteBuffer.allocate(8192); //4k
//        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

        int in = System.in.read();
        System.out.println(buffer);
        buffer.flip();
        System.out.println(buffer);

        for (int i = 0; i < buffer.limit(); i++) {
            Thread.sleep(200);
            System.out.println(buffer.get(i));
        }
    }

    /**
     *
     * @throws Exception
     */
    @Test
    private static void whatByBuffer()  {
        // 在堆上分配(还在jvm管理下)
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 在堆外分配（脱离jvm管理，直接在linux系统进程的内存模型的堆上分配）
//        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);


        System.out.println("position:"+buffer.position()); // 开始位置
        System.out.println("limit:"+buffer.limit());    // 结束位置
        System.out.println("capacity:"+buffer.capacity()); // 缓存区大小
        System.out.println("mark:"+buffer.mark()); // 记录当前的开始、结束位置和缓冲区大小

        buffer.put("123".getBytes()); // position 位置会向后移动
        System.out.println("------put:123---");
        System.out.println("mark:"+buffer);

        buffer.flip(); // 转化为读状态：position/limit 位置会重置为开始和结束
        System.out.println("------flip---");
        System.out.println("mark:"+buffer);

        buffer.get(); // position 位置会向后移动
        System.out.println("------get---");
        System.out.println("mark:"+buffer);

        buffer.compact(); // 移动元素并转化为写状态：position/limit 位置会
        System.out.println("------compact---");
        System.out.println("mark:"+buffer);

        buffer.clear();
        System.out.println("------clear---");
        System.out.println("mark:"+buffer);
    }


}
